package jacky.lanlan.song.junitx.v4.conc;

import java.lang.reflect.Method;
import java.util.Random;
import java.util.concurrent.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.junit.internal.runners.MethodRoadie;
import org.junit.internal.runners.TestMethod;
import org.junit.runner.Description;
import org.junit.runner.notification.RunNotifier;

/**
 * 可以并发执行测试用例的MethodRoadie。
 * <p>
 * <i>注意，只有通常的测试方法才可以并发测试，如果测试方法标注了Timeout，则不会有并发发生。</i>
 * @author Jacky.Song
 */
class ConcurrentMethodRoadie extends MethodRoadie{
	private Method testMethod;
	private ThreadPoolExecutor pool ;
	private Lock lock =new ReentrantLock();
	
	/**
	 * 注入要执行并发测试任务的线程池。
	 */
	public void setThreadPool(ThreadPoolExecutor pool) {
		this.pool = pool;
	}
	
	/**
	 * 注入标注了 <code>Test</code> 的原始测试方法。
	 */
	public void setOrigiTestMethod(Method testMethod) {
		this.testMethod = testMethod;
	}
	
	public ConcurrentMethodRoadie(Object test, TestMethod method, RunNotifier notifier,
			Description description) {
		super(test, method, notifier, description);
	}

	@Override
	public void runTest() {
		runBeforesThenTestThenAfters(new Runnable() {
			public void run() {
				concurrentRun(testMethod, new Runnable() {// 并发测试
					public void run() {
						runTestMethod();
					}
				});
			}
		});
	}
	
	@Override
	protected void addFailure(Throwable e) {
		try {
			lock.lock();// 并发测试的时候，会有多个线程并发调用这个方法，所以必须同步
			super.addFailure(e);
		}
		finally {
			lock.unlock();
		}
	}
	
	private void concurrentRun(Method test,Runnable task) {
		Concurrent concurrent=test.getAnnotation(Concurrent.class);
		cfgThreadPool(concurrent.load());// 每次执行新的并发测试前都需要配置线程池
		Future<Boolean> [] tasks = concurrentExecute(task,concurrent.times());
		waitUntilFinished(tasks);// 等待任务执行完毕
	}
	
	private void cfgThreadPool(int corePoolSize) {
		this.pool.setCorePoolSize(corePoolSize);
		this.pool.setMaximumPoolSize(corePoolSize+1); // TODO 参数可能需要微调
	}
	
	/**
	 * 并发执行任务。
	 * @param task 需要并发执行的任务
	 * @param times 执行次数
	 * @return 记录任务是否完成的Future
	 */
	private Future<Boolean> [] concurrentExecute(final Runnable task,int times){
		final Future<Boolean> [] futures = new Future[times];
		final CountDownLatch startSignal = new CountDownLatch(1);
		final long [] sleepTimes=this.generateSleepTime(times);
		
		for (int i = 0; i < futures.length; i++) {
			final int count=i;
			futures[i] = pool.submit(new Runnable () {
				public void run() {
					try {
						startSignal.await();// 阻塞，等待其他线程就位
						Thread.sleep(sleepTimes[count]);
						task.run();// 执行测试
					}
					catch (InterruptedException e) {
						throw new RuntimeException("并发执行测试时出现问题："+e);
					}
				}
			},true);
		}
		
		startSignal.countDown();// 发送开始信号，线程一起执行
		
		return futures;
	}

	/**
	 * 生成休眠时间，增加线程碰撞的机会。
	 */
	private long [] generateSleepTime(int times) {
		long [] sleeps=new long[times];
		Random random=new Random(System.currentTimeMillis()); //TODO Random是否真的有用？
		for (int i = 0; i < sleeps.length; i++) {
			sleeps[i]=random.nextInt(30);
		}
		return sleeps;
	}
	
	/**
	 * 阻塞Runner线程，直到并发测试执行完毕。
	 * @param futures 执行测试方法的任务结果
	 */
	private <T> void waitUntilFinished(final Future<T> [] futures) {
		final CountDownLatch finishSignal = new CountDownLatch(futures.length);
		pool.submit(new Runnable() {
			public void run() {
				for (Future<T> future : futures) {
					try {
						future.get();// 阻塞，直到任务完成
					}
					catch (Exception e) {
						throw new RuntimeException("并发执行测试时出现问题："+e);
					}
					finishSignal.countDown();// 一次测试完成，打卡下班
				}
			}
		});
		
		try {
			finishSignal.await();// Runner阻塞，直到所有并发测试完成
		}
		catch (InterruptedException e) {
			throw new RuntimeException("等待并发测试完成时出错："+e);
		}
	}
	
}